﻿#include "Mainwindow.h"

#define JSON_XML	0

Mainwindow::Mainwindow(QWidget* parent)
	:QWidget(parent)
{
	//标题重命名：
	this->setWindowTitle("qt");

	this->resize(640, 480);


	init();
}

void Mainwindow::init()
{

#if JSON_XML
	//json:

	//parseBaseJson();
	//parseJsonFromFile();
	//writeJson();
	toJsonData();

#else
	//xml:

	//parseBaseXml();
	parsewholeXml();


#endif


	//布局初始化：
	layoutinit();
}

void Mainwindow::parseBaseJson()
{
	/*
		JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集，
		采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写，同时也易于机器解析和生成，
		并有效地提升网络传输效率。


		在Qt库中，为JSON的相关操作提供了完整的类支持，包括QJsonValue，QJsonObject，QJsonArray，QJsonDocument和QJsonParseError。

		QJsonDocument是一个包装完整JSON文档的类，可以从UTF-8编码的基于文本的表示中读取该文档，并将其写入其中。
		JSON文档可以使用QJsonDocument::fromJson()将其基于文本的表示形式转换为QJsonDocument。toJson()将其转换回文本。解析器非常快速和有效，
		并将JSON转换为Qt使用的二进制表示。

		QJsonParseError类用于报告JSON解析过程中的错误。
		使用唯一的成员函数`errorString()`获取错误字符串，使用成员变量`error`获取错误码，使用成员变量`offset`获取错误位置。

		JSON对象是键值对的列表，其中键是唯一的字符串，值由QJsonValue表示。
		QJsonObject可以与QVariantMap进行转换。您可以使用size()、insert()和remove()查询(key, value)对的数量，并使用标准的c++迭代器模式遍历其内容
		QJsonObject是一个隐式共享类，只要它没有被修改，它就与创建它的文档共享数据。

		JSON数组是一个值列表。可以通过从数组中插入和删除QJsonValue来操作该列表。
		QJsonArray可以与QVariantList进行转换。您可以使用size()、insert()和removeAt()条目从它中查询条目的数量，并使用标准的C++迭代器模式迭代其内容。
		QJsonArray是一个隐式共享类，只要它没有被修改，它就与创建它的文档共享数据。

		JSON是一种存储结构化数据的格式。它有6种基本数据类型:
				`bool` QJsonValue::Bool
				`double` QJsonValue::Double
				`string` QJsonValue::String
				`array` QJsonValue::Array
				`object` QJsonValue::Object
				`null` QJsonValue::Null
		值可以表示上述任何一种数据类型。此外，QJsonValue有一个特殊标志来表示未定义的值。这可以用isUndefined()查询。
		值的类型可以用type()或访问器查询，如isBool()， isString()等。同样，可以使用toool()、toString()等方法将值转换为存储在其中的类型。
		值在内部是严格类型的，与QVariant相反，它不会尝试进行任何隐式类型转换。这意味着转换为未存储在值中的类型将返回默认构造的返回值

	*/

	//创建QJsonParseError对象		用来获取QJsonDocument创建失败产生的错误
	QJsonParseError error;


	//创建QJsonDocument对象
	QJsonDocument jdoc = QJsonDocument::fromJson(R"({"name":"lifei"})",& error);		//转义了

	//如果对象创建失败:
	if (jdoc.isNull())
	{
		qDebug() << "parse has error :> " << error.errorString() << error.offset/*
			QJsonParseError对象把QJsonDocument对象生成的json当成了一个数组，这里是返回在QJsonDocument中产生错误的（数组）位置*/;
	}


	//获取QJsonDocument根对象:
	qDebug() << jdoc.object();

	//获取QJsonDocument子对象:
	qDebug() << jdoc.object().value("name");

}

void Mainwindow::parseJsonFromFile()
{
	//创建QJsonParseError对象		用来获取QJsonDocument创建失败产生的错误
	QJsonParseError error;


	//以只读的方式打开json文件
	QFile file("./json.json");
	if (!file.open(QIODevice::ReadOnly))
	{
		qWarning() << file.fileName() << " open failed ,error is :> " << file.errorString();
		return;
	}


	//创建QJsonDocument对象
	QJsonDocument jdoc = QJsonDocument::fromJson(file.readAll(),&error);
	if (jdoc.isNull())
	{
		qDebug() << "parse json is failed ,error is :> " << error.errorString() << error.offset;
		return;
	}


	//获取QJsonDocument的根对象
	auto jobj = jdoc.object();

	//判断是否有该键：
	if (jobj.contains("name"))
	{
		if(jobj.value("name").isString())
			qDebug() << jobj.value("name");
	}
	if (jobj.contains("age"))
	{
		if (jobj.value("age").isDouble())
			qDebug() << jobj.value("age");
	}
	if (jobj.contains("sister"))
	{
		if (jobj.value("sister").isObject())
		{
			auto obj = jobj.value("sister").toObject();
			if (obj.contains("name"))
				if(obj.value("name").isString())
					qDebug() << obj.value("name");
			if (obj.contains("age"))
				if (obj.value("age").isDouble())
					qDebug() << obj.value("age");
			if (obj.contains("father"))
				if (obj.value("father").isString())
					qDebug() << obj.value("father");
		}
	}
	if (jobj.contains("father"))
	{
		if (jobj.value("father").isObject())
		{
			if (jobj.value("father").toObject().contains("name"))
				if (jobj.value("father").toObject().value("name").isString())
					qDebug() << jobj.value("father").toObject().value("name");
			if (jobj.value("father").toObject().contains("age"))
				if (jobj.value("father").toObject().value("age").isDouble())
					qDebug() << jobj.value("father").toObject().value("age");		}
	}
	if (jobj.contains("girlfriend"))
	{
		if (jobj.value("girlfriend").isArray())
		{
			for (auto item : jobj.value("girlfriend").toArray())
			{
				if (item.isString())
					qDebug() << item.toString();
				else if (item.isDouble())
					qDebug() << item.toDouble();
			}
		}
	}


	//关闭文件
	file.close();
}

void Mainwindow::writeJson()
{
	//创建QJsonDocument对象
	auto jdoc = QJsonDocument();


	//创建根对象
	QJsonObject root;

	//创建字符串类型的键值
	QJsonValue nameV = "李飞飞";
	//插入到跟对象中
	root.insert("name", nameV);

	//创建Double类型的键值
	QJsonValue ageV = 22;
	root.insert("age", ageV);

	//创建Object类型的键值
	QJsonObject fatherV;
	fatherV.insert("name", QJsonValue("李树君"));
	fatherV.insert("age", QJsonValue(45));
	fatherV.insert("sex", QJsonValue("男"));
	root.insert("father", fatherV);
	
	//创建Array类型的键值
	QJsonArray friendsV = {"旺仔","义子"};
	root.insert("friends", friendsV);


	jdoc.setObject(root);
	//打印json数据:
	qInfo() << jdoc.toJson(QJsonDocument::JsonFormat::Compact);


	//写入文件中
	QFile file("./write.json");
	if (!file.open(QIODevice::WriteOnly))
	{
		qWarning() << file.fileName() << " write is failed ,error is :> " << file.errorString();
		return;
	}

	file.write(jdoc.toJson(QJsonDocument::JsonFormat::Compact));


	file.close();
}

void Mainwindow::toJsonData()
{
	MyJsonData mydata;
	mydata.addValue("name", "吴九阴");
	mydata.addValue("age",25);


	qDebug()<<mydata.toJson();
	qDebug() << mydata.integerValue("age");
	qDebug() << mydata.stringValue("name");

}


void Mainwindow::parseBaseXml()
{
	/*
		XML——可扩展标记语言，[标准通用标记语言](通用标言是1986年国际标准化组织出版发布的一个信息管理方面的国际标准)(Extensible Markup Language)的子集，
		简称XML，是一种定义电子文档结构和描述其内容的国际标准语言，被设计用来传输和存储数据。
		可扩展标记语言与Access]，Oracle]和SQL Server等数据库不同，数据库提供了更强有力的数据存储和分析能力，例如：数据索引、排序、查找、相关一致性等，
		可扩展标记语言仅仅是存储数据。事实上它与其他数据表现形式最大的不同是：可扩展标记语言极其简单，这是一个看上去有点琐细的优点，但正是这点使它与众不同。
		XML的简单易于在任何应用程序中读/写数据，这使XML很快成为数据交换的唯一公共语言，虽然不同的应用软件也支持其他的数据交换格式，
		但不久之后它们都将支持XML，那就意味着程序可以更容易的与[Windows]()、[Mac OS]()、[Linux]()以及其他平台下产生的信息结合，然后可以很容易加载XML数据
		到程序中并分析它，并以XML格式输出结果。

		XML文件格式是纯文本格式，在许多方面类似于HTML，XML由XML元素组成，每个XML元素包括一个开始标记<>，一个结束标记</>以及两个标记之间的内容，
		例如，可以将XML元素标记为价格、订单编号或名称。标记是对文档存储格式和逻辑结构的描述。在形式上，标记中可能包括注释、引用、字符数据段、起始标记、结束标记、
		空元素、文档类型声明( DTD)和序言。

		具体规则如下：
			1、必须有声明语句。
				XML声明是XML文档的第一句，其格式如下：
							<?xml version="1.0" encoding="utf-8"?>
			2、注意大小写
				在XML文档中，大小写是有区别的。“A”和“a”是不同的标记。注意在写元素时，前后标记的大小写要保持一致。最好养成一种习惯，或者全部大写，
				或者全部小写，或者大写第一个字母，这样可以减少因为大小写不匹配而产生的文档错误。
			3、XML文档有且只有一个根元素
				良好格式的XML文档必须有一个根元素，就是紧接着声明后面建立的第一个元素，其他元素都是这个根元素的子元素，根元素完全包括文档中其他所有的元素。
				根元素的起始标记要放在所有其他元素的起始标记之前；根元素的结束标记要放在所有其他元素的结束标记之后。
			4、属性值使用引号
				在HTML代码里面，属性值可以加引号，也可以不加。但是XML规定，所有属性值必须加引号（可以是单引号，也可以是双引号，建议使用双引号），
				否则将被视为错误。
			5、所有的标记必须有相应的结束标记
				在HTML中，标记可以不成对出现，而在XML中，所有标记必须成对出现，有一个开始标记，就必须有一个结束标记，否则将被视为错误。
			6、所有的空标记也必须被关闭
				空标记是指标记对之间没有内容的标记， 由于空标记不含任何内容，所以空标记不需要开始标签和结束标签，空标记“<”标识开始，以“/>”标识结束，
				如：比如<door color="red"/>


		Qt 提供了两种读取 XML 文档的方法：
			QXmlStreamReader：一种快速的基于流的方式访问良格式 XML 文档，特别适合于实现一次解析器（所谓“一次解析器”，可以理解成我们只需读取文档一次，
			然后像一个遍历器从头到尾一次性处理 XML 文档，期间不会有反复的情况，也就是不会读完第一个标签，然后读第二个，读完第二个又返回去读第一个，
			这是不允许的）；
			DOM（Document Object Model）：将整个 XML 文档读入内存，构建成一个树结构，允许程序在树结构上向前向后移动导航，这是与另外两种方式最大的区别，
			也就是允许实现多次解析器（对应于前面所说的一次解析器）。DOM 方式带来的问题是需要一次性将整个 XML 文档读入内存，因此会占用很大内存；

		QXmlStreamReader提供了一个简单的流API来解析格式良好的XML。QXmlStreamReader从QIODevice(参见setDevice())或原始QByteArray(参见addData())读取数据。

		Qt提供了QXmlStreamWriter用于编写XML。
		流读取器的基本概念是将XML文档报告为令牌流。使用QXmlStreamReader，应用程序代码本身驱动循环，并根据需要一个接一个地从阅读器中提取令牌。
		这是通过调用readNext()来完成的，读取器从输入流中读取，直到完成下一个标记，然后返回tokenType()。然后可以使用一组方便的函数(包括isStartElement()
		和text())来检查令牌，以获得有关已读取内容的信息。这种拉式方法的最大优点是可以使用它构建递归下降解析器，这意味着可以轻松地将XML解析代码分割为不同
		的方法或类。这使得在解析XML时很容易跟踪应用程序自身的状态。

		QDomDocument类表示整个XML文档。从概念上讲，它是文档树的根，并提供对文档数据的主要访问。
		由于元素、文本节点、注释、处理指令等不能存在于文档的上下文之外，因此文档类还包含创建这些对象所需的工厂函数。创建的节点对象有一个ownerDocument()函数，
		该函数将节点对象与创建节点的上下文中的文档关联起来。最常使用的DOM类是QDomNode、QDomDocument、QDomElement和QDomText。
		解析后的XML在内部由对象树表示，可以使用各种QDom类访问这些对象树。所有QDom类只引用内部树中的对象。一旦最后一个引用它们的QDom对象或QDomDocument本身
		被删除，DOM树中的内部对象就会被删除。

	*/

	//打开响应的xml文件:
	QFile file("./xml.xml");
	if (!file.open(QIODevice::ReadOnly))
	{
		qWarning() << file.fileName() << " open failde ,error is :> " << file.errorString();
		return;
	}

	//用于获取错误的数据
	QString errMsg;					//错误信息
	int errLine;					//错误行数
	int errCol;						//错误列数

	//创建DOM文档:
	QDomDocument dom;
	auto doc = dom.setContent(&file, &errMsg, &errLine, &errCol);
	if (!doc)
	{
		qWarning() << "xml parse failed error is :> " << QString("(row:%1,col:%2)").arg(errLine).arg(errCol) << "msg:" << errMsg;
		return;
	}

	//获取版本和编码信息
	qDebug() << dom.firstChild().nodeName() << dom.firstChild().nodeValue();

	//获取指定名称为指定值的所有标签
	auto nodelist = dom.elementsByTagName("name");
	for (int i = 0; i < nodelist.size(); i++)
	{
		qDebug() << nodelist.at(i).nodeName() << nodelist.at(i).toElement().text()/*要想获取文本的话，就要先把标签转换成元素，再来获取文本*/;
	}

	//关闭文件
	file.close();
}

void Mainwindow::parsewholeXml()
{
	QFile file("./xml.xml");
	if (!file.open(QIODevice::ReadOnly))
	{
		qWarning() << file.fileName() << " open failed ,error is :> " << file.errorString();
		return;
	}

	QString errMsg;
	int errRow, errCol;

	QDomDocument dom;
	auto doc = dom.setContent(&file, &errMsg, &errRow, &errCol);
	if (!doc)
	{
		qWarning() << "xml parse failed ,error is :> " << QString("(row:%1,col:%2)").arg(errRow).arg(errCol) << "message:" << errMsg;
		return;
	}


	//拿到根节点，再把根节点转换成元素
	auto domElement = dom.documentElement();
	if (domElement.tagName() != "root")			//如果元素的元素名不是root，那就是找寻失败
	{
		qWarning() << "sry,这个xml并不是你想要的哪个版本";
		return;
	}
	qDebug() << domElement.tagName();

	//获取得到	根元素的第一个儿子元素
	auto enode = domElement.firstChild().toElement();
	//开始遍历	获取所有同级别的节点		但是还要获取所有子元素，那获取子元素，也不能每次都重复执行相同的代码，所以用递归来实现遍历
#if 0
	while (!enode.isNull())
	{
		qInfo() << enode.tagName();

		Recursion_parseXml(domElement);

		//获取同级别的下一个元素
		//enode = enode.nextSiblingElement();
		enode = enode.nextSibling().toElement();
	}
#else
	Recursion_parseXml(domElement);

#endif

	//关闭文件
	file.close();
}

void Mainwindow::Recursion_parseXml( QDomElement& element)
{
	auto enode = element.firstChild().toElement();
	while (!enode.isNull())
	{
		qInfo() << enode.tagName()<<enode.text();

		//解析属性
		if (enode.tagName() == "QWidget")
		{
			auto domNamedNodeMap = enode.attributes();
			for (int i = 0; i < domNamedNodeMap.size(); i++)
			{
				qInfo() << "attributively: " << domNamedNodeMap.item(i).toAttr().value();		//把该标签中含有QWidget的值转换成属性类型，然后再输出
			}
		}

		Recursion_parseXml(enode);

		//获取同级别的下一个元素
		enode = enode.nextSibling().toElement();
	}
}

void Mainwindow::layoutinit()
{


}
